using System;
using System.Drawing;
using System.IO;
using System.Text;

using ColladaDotNet.Pipeline.MD3;

using OpenTK;

namespace ColladaDotNet.Pipeline.BSP.Quake3
{
	public class Quake3BSPReader 
	{
		public ITransformation Transformation = NoTransformation.Value;

		public Quake3BSPFile Parse(Stream s)
		{
			var start = s.Position;
			var res = new Quake3BSPFile();
			using (var br = new BinaryReader(s, Encoding.ASCII))
			{
				res.Magic = "";
				res.Magic += br.ReadChar();
				res.Magic += br.ReadChar();
				res.Magic += br.ReadChar();
				res.Magic += br.ReadChar();

				if (res.Magic != "IBSP")
					throw new BSPFormatException("Magic should be IBSP");

				res.Version = br.ReadInt32();

				if (res.Version != 0x2e && res.Version != 0x2f)
					throw new BSPFormatException("Version should be 0x2e or 0x2f");

				ReadDirectoryEntry(br, ref res.Entities);
				ReadDirectoryEntry(br, ref res.Textures);// 	Surface descriptions.
				ReadDirectoryEntry(br, ref res.Planes);// 	Planes used by map geometry.
				ReadDirectoryEntry(br, ref res.Nodes);// 	BSP tree nodes.
				ReadDirectoryEntry(br, ref res.Leafs);// 	BSP tree leaves.
				ReadDirectoryEntry(br, ref res.Leaffaces);// 	Lists of face indices, one list per leaf.
				ReadDirectoryEntry(br, ref res.Leafbrushes);// 	Lists of brush indices, one list per leaf.
				ReadDirectoryEntry(br, ref res.Models);// 	Descriptions of rigid world geometry in map.
				ReadDirectoryEntry(br, ref res.Brushes);// 	Convex polyhedra used to describe solid space.
				ReadDirectoryEntry(br, ref res.Brushsides);// 	Brush surfaces.
				ReadDirectoryEntry(br, ref res.Vertexes);// 	Vertices used to describe faces.
				ReadDirectoryEntry(br, ref res.Meshverts);// 	Lists of offsets, one list per mesh.
				ReadDirectoryEntry(br, ref res.Effects);// 	List of special map effects.
				ReadDirectoryEntry(br, ref res.Faces);// 	Surface geometry.
				ReadDirectoryEntry(br, ref res.Lightmaps);// 	Packed lightmap data.
				ReadDirectoryEntry(br, ref res.Lightvols);// 	Local illumination data.
				ReadDirectoryEntry(br, ref res.Visdata);

				s.Position = start + res.Textures.Offest;
				res.TextureInfo = new Texture[res.Textures.Length / 72];
				if (res.TextureInfo.Length * 72 != res.Textures.Length)
					throw new BSPFormatException("texture rec should be 72 bytes");
				for (int i=0; i<res.TextureInfo.Length;++i)
				{
					res.TextureInfo[i] = ReadTexture(br);
				}

				s.Position = start + res.Planes.Offest;
				res.PlaneInfo = new Plane[res.Planes.Length / 16];
				if (res.PlaneInfo.Length * 16 != res.Planes.Length)
					throw new BSPFormatException("plane rec should be 16 bytes");
				for (int i = 0; i < res.PlaneInfo.Length; ++i)
				{
					res.PlaneInfo[i] = ReadPlane(br);
				}

				s.Position = start + res.Nodes.Offest;
				res.NodeInfo = new Node[res.Nodes.Length / 36];
				if (res.NodeInfo.Length * 36 != res.Nodes.Length)
					throw new BSPFormatException("node rec should be 36 bytes");
				for (int i = 0; i < res.NodeInfo.Length; ++i)
				{
					res.NodeInfo[i] = ReadNode(br);
				}

				s.Position = start + res.Leafs.Offest;
				res.LeafInfo = new Leaf[res.Leafs.Length / 48];
				if (res.LeafInfo.Length * 48 != res.Leafs.Length)
					throw new BSPFormatException("leaf rec should be 48 bytes");
				for (int i = 0; i < res.LeafInfo.Length; ++i)
				{
					res.LeafInfo[i] = ReadLeaf(br);
				}

				s.Position = start + res.Leaffaces.Offest;
				res.LeaffacesInfo = new int[res.Leaffaces.Length / 4];
				if (res.LeaffacesInfo.Length * 4 != res.Leaffaces.Length)
					throw new BSPFormatException("Leaffaces rec should be 4 bytes");
				for (int i = 0; i < res.LeaffacesInfo.Length; ++i)
				{
					res.LeaffacesInfo[i] = br.ReadInt32();
				}

				s.Position = start + res.Leafbrushes.Offest;
				res.LeafbrushesInfo = new int[res.Leafbrushes.Length / 4];
				if (res.LeafbrushesInfo.Length * 4 != res.Leafbrushes.Length)
					throw new BSPFormatException("Leafbrushes rec should be 4 bytes");
				for (int i = 0; i < res.LeafbrushesInfo.Length; ++i)
				{
					res.LeafbrushesInfo[i] = br.ReadInt32();
				}

				s.Position = start + res.Meshverts.Offest;
				res.MeshvertsInfo = new int[res.Meshverts.Length / 4];
				if (res.MeshvertsInfo.Length * 4 != res.Meshverts.Length)
					throw new BSPFormatException("Meshverts rec should be 4 bytes");
				for (int i = 0; i < res.MeshvertsInfo.Length; ++i)
				{
					res.MeshvertsInfo[i] = br.ReadInt32();
				}

				s.Position = start + res.Entities.Offest;
				byte[] buf = new byte[res.Entities.Length];
				s.Read(buf,0,buf.Length);
				res.EntitiesText = Encoding.ASCII.GetString(buf);

				s.Position = start + res.Models.Offest;
				res.ModelInfo = new Model[res.Models.Length / 40];
				if (res.ModelInfo.Length * 40 != res.Models.Length)
					throw new BSPFormatException("Model rec should be 40 bytes");
				for (int i = 0; i < res.ModelInfo.Length; ++i)
				{
					res.ModelInfo[i] = ReadModel(br);
				}

				s.Position = start + res.Faces.Offest;
				res.FaceInfo = new Face[res.Faces.Length / 104];
				if (res.FaceInfo.Length * 104 != res.Faces.Length)
					throw new BSPFormatException("Face rec should be 104 bytes");
				for (int i = 0; i < res.FaceInfo.Length; ++i)
				{
					var face = ReadFace(br);
					res.FaceInfo[i] = face;
					if (face.texture < 0 || face.texture >= res.Textures.Length)
						throw new BSPFormatException("bad texture id " + face.texture);
				}

				s.Position = start + res.Vertexes.Offest;
				res.VertexInfo = new Vertex[res.Vertexes.Length / 44];
				if (res.VertexInfo.Length * 44 != res.Vertexes.Length)
					throw new BSPFormatException("Vertex rec should be 44 bytes");
				for (int i = 0; i < res.VertexInfo.Length; ++i)
				{
					res.VertexInfo[i] = ReadVertex(br);
				}

				s.Position = start + res.Visdata.Offest;
				res.VisdataInfo = new Visdata();
				res.VisdataInfo = ReadVisdata(br);
			}
			return res;
		}

		private Node ReadNode(BinaryReader br)
		{
			var res = new Node();
			res.plane = br.ReadInt32();
			res.children = new int[]{br.ReadInt32(),br.ReadInt32()};

			res.mins = new int[] { br.ReadInt32(), br.ReadInt32(), br.ReadInt32() };
			res.maxs = new int[] { br.ReadInt32(), br.ReadInt32(), br.ReadInt32() }; 
			return res;
		}

		private Plane ReadPlane(BinaryReader br)
		{
			var res = new Plane();
			float x = br.ReadSingle();
			float y = br.ReadSingle();
			float z = br.ReadSingle();
			float d = br.ReadSingle();
			var n = new Vector3d(x, y, z);
			var point = n * d;

			var n1 = Transformation.TransformNormal(n);
			var p1 = Transformation.TransformPosition(point);


			res.normal = new double[] { n1.X, n1.Y, n1.Z};
			res.dist = Vector3d.Dot(n1,p1);
			return res;	
		}

		private static Visdata ReadVisdata(BinaryReader br)
		{
			var res = new Visdata();
			res.n_vecs = br.ReadInt32();
			res.sz_vecs = br.ReadInt32();
			res.vecs = br.ReadBytes(res.n_vecs * res.sz_vecs);
			return res;
		}
		private double[] ReadPosition(BinaryReader br)
		{
			var x = br.ReadSingle();
			var y = br.ReadSingle();
			var z = br.ReadSingle();
			Vector3d v = new Vector3d(x, y, z);
			v = Transformation.TransformPosition(v);
			
			return new double[] { v.X, v.Y, v.Z};
		}


		private Vertex ReadVertex(BinaryReader br)
		{
			var res = new Vertex();
			res.position = ReadPosition(br);//	Vertex position.
			res.texCoords = new float[2] { br.ReadSingle(), br.ReadSingle() };//		Vertex texture coordinates
			res.ligtmapCoords = new float[2] { br.ReadSingle(), br.ReadSingle() };//		Vertex texture coordinates
			res.normal = new double[3] { br.ReadSingle(), br.ReadSingle(), br.ReadSingle() };//	Vertex normal.
			var r = br.ReadByte();
			var g = br.ReadByte();
			var b = br.ReadByte();
			var a = br.ReadByte();
			res.color = Color.FromArgb(a, r, g, b);//	Vertex color. RGBA. 
			return res;
		}

		private static Face ReadFace(BinaryReader br)
		{
			var res = new Face();
			res.texture = br.ReadInt32();// 	Texture index.
		res.effect = br.ReadInt32();// 	Index into lump 12 (Effects), or -1.
		res.type = (FaceType)br.ReadInt32();// 	Face type. 1=polygon, 2=patch, 3=mesh, 4=billboard
		res. vertex = br.ReadInt32();// 	Index of first vertex.
		res. n_vertexes = br.ReadInt32();// 	Number of vertices.
		res. meshvert = br.ReadInt32();// 	Index of first meshvert.
		res. n_meshverts = br.ReadInt32();// 	Number of meshverts.
		res. lm_index = br.ReadInt32();// 	Lightmap index.

		res.lm_start = new int[2]{br.ReadInt32(),br.ReadInt32()};// 	Corner of this face's lightmap image in lightmap.
		res.lm_size = new int[2]{br.ReadInt32(),br.ReadInt32()};// 	Size of this face's lightmap image in lightmap.
		res.lm_origin = new float[]
		                	{
		                			br.ReadSingle(), br.ReadSingle(), br.ReadSingle()
		                	};// 	World space origin of lightmap.
		res.lm_vecs = new float[2, 3] 
		{ 
			{ br.ReadSingle(), br.ReadSingle(), br.ReadSingle() },
			{ br.ReadSingle(), br.ReadSingle(), br.ReadSingle() } 
		};// 	World space lightmap s and t unit vectors.
		res.normal = new float[]{br.ReadSingle(),br.ReadSingle(),br.ReadSingle()};// 	Surface normal.
		res.size = new int[2]{br.ReadInt32(),br.ReadInt32()};// 	Patch dimensions. 
			return res;
		}

		private Model ReadModel(BinaryReader br)
		{
			var res = new Model();
			res.mins = ReadPosition(br);
			res.maxs = ReadPosition(br);
			res.face = br.ReadInt32();
			res.n_faces = br.ReadInt32();
			res.brush = br.ReadInt32();
			res.n_brushes = br.ReadInt32();
			return res;
		}

		private static Leaf ReadLeaf(BinaryReader br)
		{
			var res = new Leaf();
			res.cluster = br.ReadInt32();
			res.area = br.ReadInt32();
			res.mins = new int[]{br.ReadInt32(),br.ReadInt32(),br.ReadInt32()};
			res.maxs = new int[] { br.ReadInt32(), br.ReadInt32(), br.ReadInt32() };
			res.leafface = br.ReadInt32();
			res.n_leaffaces = br.ReadInt32();
			res.leafbrush = br.ReadInt32();
			res.n_leafbrushes = br.ReadInt32();
			return res;
		}

		private static Texture ReadTexture(BinaryReader br)
		{
			Texture res = new Texture();
			res.name = string.Empty;
			int i = 0;
			for (i=0; i<64; ++i)
			{
				var c = br.ReadChar();
				if (c == '\0')
				{
					++i;
					break;
				}
				res.name += c;
			}
			for (; i < 64; ++i)
			{
				br.ReadChar();
			}
			res.flags = br.ReadInt32();
			res.contents = br.ReadInt32();
			return res;
		}

		private static void ReadDirectoryEntry(BinaryReader br, ref Direntry entities)
		{
			entities = new Direntry();
			entities.Offest = br.ReadInt32();
			entities.Length = br.ReadInt32();
		}
	}
}